#include "w55mh32_adc.h"
#include "w55mh32_dma.h"
#include "w55mh32_gpio.h"
#include "w55mh32_rcc.h"
#include <adc.h>
#include <stdint.h>

// Raw ADC conversion results
uint16_t g_ad_value[2];

// Filtered ADC values
uint16_t g_ad_filtered_value[2];

// Sliding average filter buffer
static uint16_t s_adc_buffer[2][FILTER_WINDOW_SIZE];

// Buffer index (static variable, records current write position)
static uint8_t s_buffer_index[2] = {0, 0};

/**
 * @brief Initialize ADC module (ADC1+DMA1, dual-channel continuous acquisition)
 * @note Configuration steps:
 * 1. Enable ADC1, GPIOA, DMA1 clocks, set ADC clock division
 * 2. Configure PA3 (channel 3) and PA4 (channel 4) as analog input
 * 3. Configure ADC1: continuous scan mode, 2 channels, right-aligned
 * 4. Configure DMA1 channel 1: automatically transfer ADC results to g_ad_value
 * array (circular mode)
 * 5. Start ADC calibration, software trigger continuous conversion, initialize
 * filter buffer
 */
void ad_init(void) {
  // Enable ADC1, GPIOA and DMA1 clocks
  RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE);
  RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
  RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);

  // Set ADC clock division
  RCC_ADCCLKConfig(RCC_PCLK2_Div6);

  // Configure PA3 and PA4 as analog input (ADC channels 3 and 4)
  GPIO_InitTypeDef gpio_init_structure;
  gpio_init_structure.GPIO_Mode = GPIO_Mode_AIN;
  gpio_init_structure.GPIO_Pin = GPIO_Pin_3 | GPIO_Pin_4;
  gpio_init_structure.GPIO_Speed = GPIO_Speed_50MHz;
  GPIO_Init(GPIOA, &gpio_init_structure);

  // Configure ADC1 regular channels (channel 3 and 4, sampling time 55.5
  // cycles)
  ADC_RegularChannelConfig(ADC1, ADC_Channel_3, 1, ADC_SampleTime_55Cycles5);
  ADC_RegularChannelConfig(ADC1, ADC_Channel_4, 2, ADC_SampleTime_55Cycles5);

  // Initialize ADC1 parameters
  ADC_InitTypeDef adc_init_structure;
  adc_init_structure.ADC_Mode = ADC_Mode_Independent;     // Independent mode
  adc_init_structure.ADC_DataAlign = ADC_DataAlign_Right; // Data right aligned
  adc_init_structure.ADC_ExternalTrigConv =
      ADC_ExternalTrigConv_None; // Software trigger
  adc_init_structure.ADC_ContinuousConvMode =
      ENABLE;                                   // Continuous conversion mode
  adc_init_structure.ADC_ScanConvMode = ENABLE; // Scan mode (multi-channel)
  adc_init_structure.ADC_NbrOfChannel = 2;      // 2 channels
  ADC_Init(ADC1, &adc_init_structure);

  // Configure DMA1 channel 1 (for automatic ADC result transfer)
  DMA_InitTypeDef dma_init_structure;
  dma_init_structure.DMA_PeripheralBaseAddr =
      (uint32_t)&ADC1->DR; // Peripheral address: ADC data register
  dma_init_structure.DMA_PeripheralDataSize =
      DMA_PeripheralDataSize_HalfWord; // 16-bit data
  dma_init_structure.DMA_PeripheralInc =
      DMA_PeripheralInc_Disable; // Disable peripheral address auto-increment
  dma_init_structure.DMA_MemoryBaseAddr =
      (uint32_t)g_ad_value; // Memory address: g_ad_value array
  dma_init_structure.DMA_MemoryDataSize =
      DMA_MemoryDataSize_HalfWord; // 16-bit data
  dma_init_structure.DMA_MemoryInc =
      DMA_MemoryInc_Enable; // Enable memory address auto-increment
  dma_init_structure.DMA_DIR =
      DMA_DIR_PeripheralSRC; // Transfer direction: peripheral to memory
  dma_init_structure.DMA_BufferSize = 2; // Transfer count: 2 channels
  dma_init_structure.DMA_Mode =
      DMA_Mode_Circular; // Circular mode (continuous transfer)
  dma_init_structure.DMA_M2M = DMA_M2M_Disable; // Disable memory to memory
  dma_init_structure.DMA_Priority = DMA_Priority_Medium; // Medium priority
  DMA_Init(DMA1_Channel1, &dma_init_structure);

  // Enable DMA and ADC functions
  DMA_Cmd(DMA1_Channel1, ENABLE); // Enable DMA1 channel 1
  ADC_DMACmd(ADC1, ENABLE);       // Enable ADC to trigger DMA
  ADC_Cmd(ADC1, ENABLE);          // Enable ADC1

  // Perform ADC calibration
  ADC_ResetCalibration(ADC1);
  while (ADC_GetResetCalibrationStatus(ADC1) == SET)
    ; // Wait for calibration reset to complete
  ADC_StartCalibration(ADC1);
  while (ADC_GetCalibrationStatus(ADC1) == SET)
    ; // Wait for calibration to complete

  // Software trigger ADC continuous conversion
  ADC_SoftwareStartConvCmd(ADC1, ENABLE);

  // Initialize filter buffer (clear to zero)
  for (uint8_t i = 0; i < 2; i++) {
    for (uint8_t j = 0; j < FILTER_WINDOW_SIZE; j++) {
      s_adc_buffer[i][j] = 0;
    }
  }
}

/**
 * @brief Apply sliding average filter to ADC values of 2 channels
 * @note Processing logic:
 * 1. Store new raw values into buffer (s_adc_buffer)
 * 2. Update buffer index (circularly overwrite old values)
 * 3. Calculate average of all values in window, store in g_ad_filtered_value
 */
void ad_filter_process(void) {
  for (uint8_t channel = 0; channel < 2; channel++) {
    // Store new ADC value into buffer
    s_adc_buffer[channel][s_buffer_index[channel]] = g_ad_value[channel];

    // Update buffer index (circular count, 0~FILTER_WINDOW_SIZE-1)
    s_buffer_index[channel] =
        (s_buffer_index[channel] + 1) % FILTER_WINDOW_SIZE;

    // Calculate sliding average value
    uint32_t sum = 0;
    for (uint8_t i = 0; i < FILTER_WINDOW_SIZE; i++) {
      sum += s_adc_buffer[channel][i];
    }
    g_ad_filtered_value[channel] = sum / FILTER_WINDOW_SIZE;
  }
}
